{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению\n",
    "Автор материала: Жеглов Дмитрий студент 4 курса механико-математического факультета МГУ. Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# <center>Word2Vec</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Всем привет! На протяжении данного курса мы рассмотрели различные алгоритмы машинного обучения. Но что делать если нам даны нечисловые данные и не хочется сильно раздувать пространство признаков? Для этой задачи мы рассмотрим популярную технологию Word2Vec. Опишем теорию и испытаем силы алгоритма в конкурсах [\"Catch Me If You Can\"](https://inclass.kaggle.com/c/catch-me-if-you-can-intruder-detection-through-webpage-session-tracking) и [Прогноз популярности статьи на Хабре](https://inclass.kaggle.com/c/howpop-habrahabr-favs), которые проводятся в рамках данного курса."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# План:\n",
    "\n",
    "1. Что такое Word2Vec?\n",
    "    - описание\n",
    "    - косинусная мера\n",
    "    - гипепараметры модели\n",
    "2. Архитектура нейронной сети\n",
    "    - Continuous Bag of Words (CBOW)\n",
    "    - Skip-Gram\n",
    "3. Уменьшение вычислительной сложности\n",
    "    - Hierarchical Softmax\n",
    "    - Negative Sampling\n",
    "4. Применение на данных\n",
    "    - предобработка\n",
    "    - обучение модели\n",
    "    - тестирование\n",
    "5. Вывод\n",
    "6. Полезные ссылки"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Что такое Word2Vec?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Word2Vec – одна из технологий анализа семантики естественных языков, которая основана на векторном представлении слов согласно их семантической близости. Был разработан группой исследователей Google в 2013 году, активно применяется для семантического анализа текстов и похожих задачах.\n",
    "Word2vec в качестве входных данных принимает текстовый корпус и гиперпараметры, о которых поговорим позднее. Далее названия гиперпараметров и их дефолтные значения будут указаны в соответствии с реализацией Word2Vec на open-source платформе Gensim.\n",
    "\n",
    "Суть алгоритма заключается в том, что он каждому слову сопоставляет числовой вектор определенной длины таким образом, чтобы близкие слова соответствуют близким векторам. Мерой близости слов выступает их контекстная близость т.е. близкие слова встречаются в тексте рядом с одинаковыми словами. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Например слова \"девушка\" и \"женщина\" будут часто употребляться со словами: \"красивая\", \"прекрасная\", \"чудесная\", \"заботливая\", а слова \"мужчина\" и \"парень\" будут часто употребляться со словами: \"храбрый\", \"сильный\", \"смелый\", \"могучий\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"../../img/word_vector.png\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Расстоянием между векторами измеряется при помощи [косинусного сходства](https://ru.wikipedia.org/wiki/%D0%92%D0%B5%D0%BA%D1%82%D0%BE%D1%80%D0%BD%D0%B0%D1%8F_%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C) (cosine similarity). Косинусное сходство - это мера сходства между двумя векторами предгильбертового пространства, которая используется для измерения косинуса угла между ними. Косинусная мера между векторами x и y длины n вычисляется по формуле:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$cos(\\theta)=\\frac{(x,y)}{|x| |y|}=\\frac{\\sum\\limits_{i=1}^{n}x_i y_i}{\\sqrt{\\sum\\limits_{i=1}^{n}x_i^2} \\sqrt{\\sum\\limits_{i=1}^{n}y_i^2}}$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Обучаясь, Word2Vec максимизирует косинусную меру близости между векторами слов, которые встречаются в похожих контекстах и минимизирует косинусную меру между словами, которые не встречаются рядом. Word2Vec получает на вход слово, а на выход передает координаты вектора, соответствующие данному слову.\n",
    "\n",
    "Полученные вектора можно складывать или вычитать друг из друга, сохраняя семантические связи."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "<img src=\"../../img/work_with_vector.png\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Некоторый недостаток заключается в том, что мы не имеем понятия за что отвечают полученные признаки. Т.е. у нас нет интерпретации, как на картинке ниже. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"../../img/value_vector.png\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В Word2Vec можно использовать две различных архитектуры нейронной сети с помощью которых осуществляется перевод слова в вектор: \n",
    "\n",
    "- Continuous Bag of Words (CBOW);\n",
    "- Skip-gram.\n",
    "\n",
    "Выбор одной из этих моделей выполняется с помощью гиперпараметра ‘sg’. По умолчанию sg=0 и используется модель CBOW, при sg=1 используется модель Skip-gram.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Другим гиперпараметром является размер окна, в котором рассматривается контекст данного слова. В данной реализации используется параметр ‘window’, который определяет максимальное количество слов между данным словом и соседним внутри предложения. Слова, стоящие от данного дальше этого значения, не будут рассматриваться как его контекст. Какое слово стоит в тексте ближе, а какое дальше от данного слова, не учитывается, при условии, что оба слова попали в окно. Вот пример с параметром window = 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"../../img/window_ex.png\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Еще один важный гиперпараметр ‘size’ - размерность векторов, соответствующих словам. Если его величина мала, то модель получается грубой и плохо отображает связь между словами внутри данного массива текстов. А при большом значении роль машинного обучения теряется, и сопоставление словам векторов может превратиться в унитарное кодирование слов (one-hot encoding).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Помимо этого есть такие параметры как:\n",
    "\n",
    "alpha - начальный коэффициент скорости обучения (будет линейно падать в ходе обучения).\n",
    "\n",
    "seed = семя для воспроизводимости результатов;\n",
    "\n",
    "min_count = минимальная частота слова, чтобы оно было учтено; \n",
    "\n",
    "max_vocab_size = ограничение на выделение ОЗУ для словаря. 10млн. слов занимают примерно 1GB RAM;\n",
    "\n",
    "workers = число ядер используемых для обучения, повышает скорость;\n",
    "\n",
    "iter = число итераций на каждый текст (по умолчанию 5);\n",
    "\n",
    "sorted_vocab = 1 (значение по умолчанию),то сортирует словарь по убыванию частоты перед назначением слову индекса.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Архитектура нейронной сети"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Теперь более подробно рассмотрим процесс обучения и архитектуру нейронных сетей.\n",
    "Пусть $V$ - количество слов в словаре, $h$ - размер окна (количество соседних слов, рассматриваемых как контекст данного слова), $N$ - размерность искомых векторов. \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Continuous Bag of Words (CBOW)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Когда данных мало лучше использовать CBOW, т.к. она менее склонна к переобучению. Отметим, что CBOW работает быстрее, чем skip-gram, но хуже учитывает редкие слова. В данном подходе нейронная сеть предсказывает исходное слово по его контексту ($h$ соседним словам). Нейронная сеть состоит из трех слоев: входной, скрытый, выходной.\n",
    "\n",
    "На вход сети подаются $h$ векторов размерности $V$: $x_i=(x_i^1,x_i^2,...,x_i^V), i=1...h$, где $x_i^j=1$, если данное слово является $j$-ым словом из словаря, $x_i^k=0$ для $k\\neq j$. На выходе имеем один вектор размерности $V$: $y=(y^1,y^2,...,y^V)$. На обучающейся выборке $y^j=1$, если предсказываемое слово является $j$-ым словом из словаря, $y^k=0$ для $k\\neq j$. То есть нейронная сеть имеет $h\\times V$ нейронов на входном слое и $V$ нейронов на выходном слое.\n",
    "\n",
    "На скрытом слое сети $N$ нейронов. Именно с помощью весов, расставленных перед нейронами этого слоя, мы получим координаты векторных представлений слов. Функция активации на скрытом уровне — линейная, на выходном уровне — софтмакс (softmax).\n",
    "\n",
    "Сначала рассмотрим простейший случай, когда  $h=1$.То есть будем предсказывать слово $y$ только по одному его соседу $x$. Тогда архитектура сети примет вид, изображенный на схеме:\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"../../img/h1.png\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Обозначим веса между входным и скрытым уровнями за $W$ - матрица размерности $V\\times N$. Учитывая, что функция активации на скрытом слое линейная, вектор выходов размерности $N$ на скрытом уровне имеет вид: $$v=Wx$$\n",
    "\n",
    "Обозначим веса между скрытым и выходным уровнями за $W'$ - матрица размерности $N\\times V$. Пусть $w'_j$ - $j$-ая строка матрицы $W'$. Тогда входной сигнал $j$-ого нейрона на выходном уровне имеет вид: $$u^j=w'_j v$$\n",
    "\n",
    "Так как на выходном уровне используется функция активации softmax, выходной сигнал на  $j$-ом нейроне выходного слоя принимает вид:\n",
    "\n",
    "$$y^j=\\frac{exp(u^j)}{\\sum\\limits_{k=1}^{V} exp(u^k)}$$\n",
    "\n",
    "Такое представление имеет вероятностную интерпретацию. Пусть $w_I$ - входное слово, а $w_O$ - выходное слово. Обозначим получившееся выражение для $y^j$ за $p(w_j|w_I)$. Обучаясь, сеть максимизирует $y^{j*}=\\frac{exp(u^{j*})}{\\sum\\limits_{k=1}^{V} exp(u^k)}=p(w_O|w_I)$, при условии что $w_O = w_{j*}$.\n",
    "\n",
    "Сеть обучается методом обратного распространения ошибки (backpropagation), то есть сначала корректируются веса $W'$, а затем $W$. Минимизируемый функционал потерь имеет вид:\n",
    "$$L=-log \\ p(w_O|w_I)$$\n",
    "\n",
    "После того, как процесс обучения завершился, $V$ строк длины $N$ матрицы $W'$ дадут нам  координаты векторов, представляющих слова из словаря.\n",
    "\n",
    "Теперь рассмотрим общий случай для произвольного $h$. То есть будем предсказывать слово $y$ только по соседним словам $x_i, i=1...h$. Тогда архитектура сети примет вид, изображенный на схеме:\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"../../img/cbow.png\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Обозначим веса между входными и скрытым уровнями за $W$ - матрица размерности $Vh\\times N$. Учитывая, что функция активации на скрытом слое линейная, вектор выходов размерности $N$ на скрытом уровне имеет вид: $$v= \\frac{1}{h} W (x_1+...+x_h)$$\n",
    "\n",
    "Далее, аналогично случаю $h=1$ получаем входной сигнал $j$ -ого нейрона на выходном слое сети $$u^j=w'_j v,$$\n",
    "где $w'_j$ - $j$-ая строка матрицы весов между скрытым и выходным уровнями $W'$.\n",
    "\n",
    "Пусть входные слова (соседние слова предсказываемого) - $w_{I,1},...,w_{I,h}$. Тогда выходной сигнал на  $j$-ом нейроне выходного слоя принимает вид:\n",
    "\n",
    "$$y^j=\\frac{exp(u^j)}{\\sum\\limits_{k=1}^{V} exp(u^k)}=p(w_j|w_{I,1},...,w_{I,h})$$\n",
    "\n",
    "Как и ранее, обучаясь, сеть максимизирует $y^{j*}=\\frac{exp(u^{j*})}{\\sum\\limits_{k=1}^{V} exp(u^k)}=p(w_O= w_{j*}|w_{I,1},...,w_{I,h})$, при условии что $w_O = w_{j*}$.\n",
    "\n",
    "Минимизируемый функционал потерь имеет вид:\n",
    "$$L=-log \\ p(w_O|w_{I,1},...,w_{I,h})$$\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Skip-Gram"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В случае когда данных много, то лучше использовать Skip-Gram. Данный подход обратен CBOW: по заданному слову предсказывается его контекст ($h$ соседних слов). Сеть также состоит из трех слоев: входной, скрытый, выходной.\n",
    "\n",
    "На вход сети подается вектор размерности $V$: $x=(x^1,x^2,...,x^V)$, где $x^j=1$, если данное слово является $j$-ым словом из словаря, $x^k=0$ для $k\\neq j$. На выходе имеем $h$ векторов размерности $V$: $y_i=(y_i^1,y_i^2,...,y_i^V), i=1...h$. На обучающейся выборке $y_i^j=1$, если предсказываемое $i$-е слово из окна является $j$-ым словом из словаря, $y_i^k=0$ для $k\\neq j$. То есть нейронная сеть имеет $V$ нейронов на входном слое и $h\\times V$ нейронов на выходном слое.\n",
    "\n",
    "На скрытом слое сети, как и ранее, $N$ нейронов, а функции активации на скрытом уровне — линейная, на выходном уровне — софтмакс (softmax).\n",
    "\n",
    "Схема архитектуры сети изображена на схеме:\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"../../img/skip.png\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Обозначим веса между входным и скрытым уровнем за $W$ - матрица размерности $V\\times N$. Учитывая, что функция активации на скрытом слое линейная, вектор выходов размерности $N$ на скрытом уровне имеет вид: $$v=Wx.$$\n",
    "\n",
    "Обозначим веса между скрытым и выходными уровнями за $W'$ - матрица размерности $N \\times hV$. Пусть $w'_j$ - $j$-ая строка матрицы $W'$. Тогда входной сигнал $(i V+j)$-ого нейрона (соответствует вероятности того, что $i$-е слово из контекста это $j$ слово из словаря) на выходном уровне имеет вид: $$u^{i,j}=u^j=w'_j v , i=1...h, j=1...V$$\n",
    "\n",
    "$u^{i,j}$ одинаково для всех $i$, так как слова в контексте заданного слова равнозначны, то есть все нейроны имеют одинаковые веса.\n",
    "\n",
    "Пусть $w_I$ - входное слово, а $w_{O,i}$ - $i$-е выходное слово, тогда выходной сигнал на  $(i V+j)$-ом нейроне выходного слоя имеет вид:\n",
    "\n",
    "$$y^j=\\frac{exp(u^j)}{\\sum\\limits_{n=1}^{V} exp(u^{i V+n})}=p(w_{O,i}=w_j|w_I)$$\n",
    "\n",
    "Обучаясь, сеть максимизирует $p(w_{O,1}=w_{j_1},...,w_{O,h}=w_{j_h}|w_I)=\\prod\\limits_{k=1}^{h}p(w_{O,k}=w_{j_k}|w_I)$, при условии что $w_{O,k} = w_{j_k}, k=1...h$.\n",
    "\n",
    "Минимизируемый функционал потерь имеет вид:\n",
    "$$L=-log \\ p(w_{O,1},...,w_{O,h}|w_I)$$\n",
    "\n",
    "Воспользуемся формулой Байеса:\n",
    "$$ p(w_{O,1},...,w_{O,h}|w_I)=\\frac{p(w_I|w_{O,1},...,w_{O,h}) p(w_{O,1},...,w_{O,h})}{p(w_I)}$$\n",
    "Из этого выражения видно, что увеличение  $p(w_{O,1},...,w_{O,h}|w_I)$  влечет увеличение $p(w_I|w_{O,1},...,w_{O,h})$. Это соображение говорит о схожести описанных подходов.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Уменьшение вычислительной сложности"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Несложно догадаться, что без дополнительных доработок алгоритм будет работать очень медленно, так как размерность словаря $V$ может достигать очень больших значений (нескольких сотен тысяч). Так как сеть обучается методом обратного распространения ошибки, необходимо вычислять градиент на двух шагах, что очень затратно вычислительно.\n",
    "\n",
    "Рассмотрим два метода решения этой проблемы: иерархический софтмакс (Hierarchical Softmax) и Negative Sampling. \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Hierarchical Softmax"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Этот метод помогает эффективно вычислить значение softmax, используя бинарное дерево. По всем словам в словаре строится дерево Хаффмана. В полученном дереве $V$ слов располагаются на листьях дерева.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"../../img/hierarsofr.png\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На рисунке изображен пример бинарного дерева. Жирным выделен путь от корня до слова $w_2$. Длину пути обозначим $L(w)$, а $j$-ую вершину на пути к слову $w$ обозначим через $n(w,j)$. Можно доказать, что внутренних вершин (не листьев) $V − 1$.\n",
    "\n",
    "С помощью иерархического softmax вектора $v_{n(w,j)}$ предсказывается для $V-1$ внутренних вершин. А вероятность того, что слово $w$ будет выходным словом (в зависимости от того, что мы предсказываем: слово из контекста или заданное слово по контексту) вычисляется по формуле:\n",
    "$$p(w=w_o)=\\prod_{j=1}^{L(w)-1}\\sigma([n(w,j+1)=lch(n(w,j))] v_{n(w,j)}^T u)$$\n",
    "где $\\sigma()$ - функция softmax; $[true]=1,[false]=-1$; $lch(n)$ - левый сын вершины $n$; $u=v_{w_I}$, если используется метод skip-gram, $u=\\frac{1}{h} \\sum\\limits_{k=1}^{h} v_{w_{I,k}}$, если используется CBOW.\n",
    "\n",
    "Формулу можно интуитивно понять, представив, что на каждом шаге мы можем пойти налево или направо с вероятностями:\n",
    "$$p(n,left)=\\sigma(v_n^T u)$$\n",
    "$$p(n,right)=1-p(n,left)=1-\\sigma(v_n^T u)=\\sigma(-v_n^T u)$$\n",
    "Затем на каждм шаге вероятности перемножаются ($L(w)-1$ шагов) и получается искомая формула.\n",
    "\n",
    "При использовании простого softmax для подсчета вероятности слова, приходилось вычислять нормирующую сумму по всем словам из словаря, требовалось $O(V)$ операций. Теперь же вероятность слова можно вычислить при помощи последовательных вычислений, которые требуют $O(log(V))$.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Negative Sampling"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Идея этого метода состоит в том, чтобы пересчитывать функционал потерь не по всем словам из словаря, а только по меньшему количеству слов. Тем самым пересчитываться будут вектора не всех слов из словаря, а только некоторого подмножества.\n",
    "\n",
    "Для реализации данной идеи функционал потерь был изменен следующим образом: выходной вектор $w_O$ остается и обновляется, также нужно случайно выбрать  $K$ векторов слов (negative samples), которые не являются подходящими нам (не из контекста входного слова или входных слов). Предполагается, что модель устойчива к шуму и что negative samples имеют некоторое распределение $P_n (w)$, вид распределения задается произвольно.\n",
    "\n",
    "Функция потерь принимает вид:\n",
    "$$L=- log\\sigma(v_{w_O}^T u)-\\sum_{w \\in W_{neg}} log\\sigma(-v_{w}^T u)$$\n",
    "где $W_{neg}=\\{w_j|j=1,...,K\\}$ - $K$ случайно выбранных из словаря слов согласно распределению $P_n (w)$; $u=v_{w_I}$, если используется метод skip-gram, $u=\\frac{1}{h} \\sum\\limits_{k=1}^{h} v_{w_{I,k}}$, если используется CBOW.\n",
    "\n",
    "После нескольких случайных генераций слов negative samples алгоритм сходится."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Если параметр модели hs = 1, то будет использован hierarchical softmax . Если hs = 0 (по умолчанию),то будет использован negative sampling."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Применим на наборе данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Если вы не имеете достаточно много данных, то ваша модель может ничего полезного не выучить. Поэтому иногда имеет смысл воспользоваться уже предобученной моделью, например на wiki или новостях. Скачать можно например c [code.google](https://code.google.com/archive/p/word2vec/)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Но в нашем случае необходимо учитывать особенности последовательностей сайтов. Поэтому предобученный Word2Vec нам не поможет."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# загрузим библиотеки и установим опции\n",
    "from __future__ import division, print_function\n",
    "\n",
    "# отключим всякие предупреждения Anaconda\n",
    "import warnings\n",
    "\n",
    "warnings.filterwarnings('ignore')\n",
    "#%matplotlib inline\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.metrics import roc_auc_score"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "скачать данные можно со страницы соревнования [\"Catch Me If You Can\"](https://inclass.kaggle.com/c/catch-me-if-you-can-intruder-detection-through-webpage-session-tracking)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# загрузим обучающую и тестовую выборки\n",
    "train_df = pd.read_csv('data/train_sessions.csv')#,index_col='session_id')\n",
    "test_df = pd.read_csv('data/test_sessions.csv')#, index_col='session_id')\n",
    "\n",
    "# приведем колонки time1, ..., time10 к временному формату\n",
    "times = ['time%s' % i for i in range(1, 11)]\n",
    "train_df[times] = train_df[times].apply(pd.to_datetime)\n",
    "test_df[times] = test_df[times].apply(pd.to_datetime)\n",
    "\n",
    "# отсортируем данные по времени\n",
    "train_df = train_df.sort_values(by='time1')\n",
    "\n",
    "# посмотрим на заголовок обучающей выборки\n",
    "train_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sites = ['site%s' % i for i in range(1, 11)]\n",
    "#заменим nan на 0\n",
    "train_df[sites] = train_df[sites].fillna(0).astype('int').astype('str')\n",
    "test_df[sites] = test_df[sites].fillna(0).astype('int').astype('str')\n",
    "#создадим тексты необходимые для обучения word2vec\n",
    "train_df['list'] = train_df['site1']\n",
    "test_df['list'] = test_df['site1']\n",
    "for s in sites[1:]:\n",
    "    train_df['list'] = train_df['list']+\",\"+train_df[s]\n",
    "    test_df['list'] = test_df['list']+\",\"+test_df[s]\n",
    "train_df['list_w'] = train_df['list'].apply(lambda x: x.split(','))\n",
    "test_df['list_w'] = test_df['list'].apply(lambda x: x.split(','))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#В нашем случае предложение это набор сайтов, которые посещал пользователь\n",
    "#нам необязательно переводить цифры в названия сайтов, т.к. алгоритм будем выявлять взаимосвязь их друг с другом\n",
    "train_df['list_w'][10]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# подключим word2vec\n",
    "from gensim.models import word2vec"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#объединим обучающую и тестовую выборки и обучим нашу модель на всех данных \n",
    "#с размером окна в 6=3*2(длина предложения 10 слов) и итоговыми векторами размерности 300, параметр workers отвечает за количество ядер\n",
    "test_df['target'] = -1\n",
    "data = pd.concat([train_df,test_df],axis=0)\n",
    "\n",
    "model = word2vec.Word2Vec(data['list_w'], size=300, window=3, workers=4)\n",
    "#создадим словарь со словами и соответствующими им векторами\n",
    "w2v = dict(zip(model.wv.index2word, model.wv.syn0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Т.к. сейчас мы каждому слову сопоставили вектор, то нужно решить что сопоставить целому предложению из слов.\n",
    "Один из возможных вариантов - это просто усреднить все слова в предложении и получить некоторый смысл всего предложения (если слова нет в тексте, то берем нулевой вектор)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class mean_vectorizer(object):\n",
    "    def __init__(self, word2vec):\n",
    "        self.word2vec = word2vec\n",
    "        self.dim = len(next(iter(w2v.values())))\n",
    "    \n",
    "    def fit(self, X):\n",
    "        return self \n",
    "\n",
    "    def transform(self, X):\n",
    "        return np.array([\n",
    "            np.mean([self.word2vec[w] for w in words if w in self.word2vec] \n",
    "                    or [np.zeros(self.dim)], axis=0)\n",
    "            for words in X\n",
    "        ])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_mean=mean_vectorizer(w2v).fit(train_df['list_w']).transform(train_df['list_w'])\n",
    "data_mean.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Т.к. мы получили distributed representation, то никакая фича ничего не значит, а значит лучше всего покажут себя линейные комбинации. Например нейронные сети"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Воспользуемся валидацией, как в 4 дз курса ODS\n",
    "def split(train,y,ratio):\n",
    "    idx = round(train.shape[0] * ratio)\n",
    "    return train[:idx, :], train[idx:, :], y[:idx], y[idx:]\n",
    "y = train_df['target']\n",
    "Xtr, Xval, ytr, yval = split(data_mean, y,0.8)\n",
    "Xtr.shape,Xval.shape,ytr.mean(),yval.mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from keras import regularizers\n",
    "from keras.layers import Activation, Dense, Dropout, Input\n",
    "# подключим библиотеки keras \n",
    "from keras.models import Model, Sequential\n",
    "from keras.preprocessing.text import Tokenizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#опишем нейронную сеть\n",
    "model = Sequential()\n",
    "model.add(Dense(128, input_dim=(Xtr.shape[1])))\n",
    "model.add(Activation('relu'))\n",
    "model.add(Dropout(0.5))\n",
    "model.add(Dense(1))\n",
    "model.add(Activation('sigmoid'))\n",
    "model.compile(loss='binary_crossentropy',\n",
    "              optimizer='adam',\n",
    "              metrics=['binary_accuracy'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "history = model.fit(Xtr, ytr,\n",
    "                    batch_size=128,\n",
    "                    epochs=10,\n",
    "                    validation_data=(Xval, yval),\n",
    "                    class_weight='auto',\n",
    "                    verbose=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "classes = model.predict(Xval, batch_size=128)\n",
    "roc_auc_score(yval, classes)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Получили результат сопоставимый, с нашим дедлайном 4 дз(0.919524709674), где использовали logistic regressiom на one-hot-encoding сайтов. Но при этом мы уменьшили размерность пространства до 300. Значит Word2Vec смог выявить зависимости между сессиями.\n",
    "Посмотрим, что произойдет с деревянным алгоритмом."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import xgboost as xgb"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dtr = xgb.DMatrix(Xtr, label= ytr,missing = np.nan)\n",
    "dval = xgb.DMatrix(Xval, label= yval,missing = np.nan)\n",
    "watchlist = [(dtr, 'train'), (dval, 'eval')]\n",
    "history = dict()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "params = {\n",
    "    'max_depth': 26,\n",
    "    'eta': 0.025,\n",
    "    'nthread': 4,\n",
    "    'gamma' : 1,\n",
    "    'alpha' : 1,\n",
    "    'subsample': 0.85,\n",
    "    'eval_metric': ['auc'],\n",
    "    'objective': 'binary:logistic',\n",
    "    'colsample_bytree': 0.9,\n",
    "    'min_child_weight': 100,\n",
    "    'scale_pos_weight':(1)/y.mean(),\n",
    "    'seed':7\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_new = xgb.train(params, dtr, num_boost_round=200, evals=watchlist,evals_result=history, verbose_eval=20)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видим, что алгоритм сильно подстраивается под обучающую выборку, поэтому возможно лучше использовать линейные алгоритмы.\n",
    "Посмотрим, что покажет обычный LogisticRegression."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.linear_model import LogisticRegression\n",
    "\n",
    "\n",
    "def get_auc_lr_valid(X, y, C=1, seed=7, ratio = 0.8):\n",
    "    # разделим выборку на обучающую и валидационную\n",
    "    idx = round(X.shape[0] * ratio)\n",
    "    # обучение классификатора\n",
    "    lr = LogisticRegression(C=C, random_state=seed, n_jobs=-1).fit(X[:idx], y[:idx])\n",
    "    # прогноз для валидационной выборки\n",
    "    y_pred = lr.predict_proba(X[idx:, :])[:, 1]\n",
    "    # считаем качество\n",
    "    score = roc_auc_score(y[idx:], y_pred)\n",
    "    \n",
    "    return score"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "get_auc_lr_valid(data_mean, y, C=1, seed=7, ratio = 0.8)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Результат LogisticRegression отличается от NN на 0.02, что достаточно существенно.\n",
    "Попробуем улучшить результаты."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Теперь вместо обычного среднего, чтобы учесть частоту, с которой слово встречается в тексте, возьмем взвешенное среднее. В качестве весов возьмем idf меру слова.  Idf это инверсия частоты, с которой некоторое слово встречается в других документах. Учёт idf уменьшает вес широкоупотребительных слов и увеличивает вес более уникальных слов, которые могут достаточно точно указать на то к какому классу относится текст. В нашем случае, кому принадлежит последовательность посещенных сайтов.\n",
    "$$idf(w,D)=log \\frac{|D|}{|{\\{d \\in D | w \\in d\\}}|}$$\n",
    "где $|D|$ - общее число документов, $\\{d \\in D | w \\in d\\}$ - число документов из $D$, в которых встречается слово $w$.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import defaultdict\n",
    "\n",
    "#пропишем класс выполняющий tfidf преобразование.\n",
    "from sklearn.feature_extraction.text import TfidfVectorizer\n",
    "\n",
    "\n",
    "class tfidf_vectorizer(object):\n",
    "    def __init__(self, word2vec):\n",
    "        self.word2vec = word2vec\n",
    "        self.word2weight = None\n",
    "        self.dim = len(next(iter(w2v.values())))\n",
    "\n",
    "    def fit(self, X):\n",
    "        tfidf = TfidfVectorizer(analyzer=lambda x: x)\n",
    "        tfidf.fit(X)\n",
    "        # if a word was never seen - it must be at least as infrequent\n",
    "        # as any of the known words - so the default idf is the max of \n",
    "        # known idf's\n",
    "        max_idf = max(tfidf.idf_)\n",
    "        self.word2weight = defaultdict(\n",
    "            lambda: max_idf,\n",
    "            [(w, tfidf.idf_[i]) for w, i in tfidf.vocabulary_.items()])\n",
    "\n",
    "        return self\n",
    "\n",
    "    def transform(self, X):\n",
    "        return np.array([\n",
    "                np.mean([self.word2vec[w] * self.word2weight[w]\n",
    "                         for w in words if w in self.word2vec] or\n",
    "                        [np.zeros(self.dim)], axis=0)\n",
    "                for words in X\n",
    "            ])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_mean = tfidf_vectorizer(w2v).fit(train_df['list_w']).transform(train_df['list_w'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверим изменилось ли качество LogisticRegression."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "get_auc_lr_valid(data_mean, y, C=1, seed=7, ratio = 0.8)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "видим прирост на 0.07, значит скорее всего взвешенное среднее помогает лучше отобразить смысл всего предложения через word2vec"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# habr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Испробуем силы алгоритма непосредственно на текстовых данных статей хабра. Я преобразовал данные в csv таблицы. Скачать их вы можете с [train](https://yadi.sk/d/hAhCuetI3JPouk),[test](https://yadi.sk/d/mLMZZtN63JPouc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Xtrain = pd.read_csv('data/train_content.csv')\n",
    "Xtest = pd.read_csv('data/test_content.csv')\n",
    "print(Xtrain.shape,Xtest.shape)\n",
    "Xtrain.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Будем обучать модель на всем содержании статьи. Для этого совершим некоторые преобразования над текстом."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Напишем функцию, которая будет преобразовывать тестовую статью в лист из слов необходимый для обучения Word2Vec.\n",
    "Функция получает строку, в которой содержится весь текстовый документ.\n",
    "\n",
    "1)Сначала функция будет удалять все символы кроме букв верхнего и нижнего регистра;\n",
    "\n",
    "2)Затем преобразовывает слова к нижнему регистру;\n",
    "\n",
    "3)После чего удаляет стоп слова из текста, т.к. они не несут никакой информации о содержании;\n",
    "\n",
    "4)Лемматизация, процесс приведения словоформы к лемме — её нормальной (словарной) форме.\n",
    "\n",
    "Функция возвращает лист из слов"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import re\n",
    "\n",
    "import pymorphy2\n",
    "from nltk.corpus import stopwords\n",
    "#подключим необходимые библиотеки\n",
    "#\n",
    "from sklearn.metrics import mean_squared_error\n",
    "\n",
    "morph = pymorphy2.MorphAnalyzer()\n",
    "\n",
    "stops = set(stopwords.words(\"english\")) | set(stopwords.words(\"russian\"))\n",
    "def review_to_wordlist(review):\n",
    "    #1)\n",
    "    review_text = re.sub(\"[^а-яА-Яa-zA-Z]\",\" \", review)\n",
    "    #2)\n",
    "    words = review_text.lower().split()\n",
    "    #3)\n",
    "    words = [w for w in words if not w in stops]\n",
    "    #4)\n",
    "    words = [morph.parse(w)[0].normal_form for w in words ]\n",
    "    return(words)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Лемматизация занимает много времени, поэтому ее можно убрать в целях более быстрых подсчетов."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Преобразуем время\n",
    "Xtrain['date'] = Xtrain['date'].apply(pd.to_datetime)\n",
    "Xtrain['year'] = Xtrain['date'].apply(lambda x: x.year)\n",
    "Xtrain['month'] = Xtrain['date'].apply(lambda x: x.month)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Будем обучаться на 2015 году, а валидироваться на первых 4 месяцах 2016, т.к. в нашей тестовой выборке представлены данные за первые 4 месяца 2017 года. Более правдивую валидацию можно сделать, идя по годам увеличивая нашу обучающую выборку и смотря качество на первых четырех месяцах следующего года"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Xtr = Xtrain[Xtrain['year']==2015]\n",
    "Xval = Xtrain[(Xtrain['year']==2016)& (Xtrain['month']<=4)]\n",
    "ytr = Xtr['favs_lognorm']\n",
    "yval = Xval['favs_lognorm']\n",
    "Xtr.shape,Xval.shape,ytr.mean(),yval.mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = pd.concat([Xtr,Xval],axis = 0,ignore_index = True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#у нас есть nan, поэтому преобразуем их к строке\n",
    "data['content_clear'] = data['content'].apply(str)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "data['content_clear'] = data['content_clear'].apply(review_to_wordlist)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "моя оперативная память закончилась, я сохранил data и подгрузил ее после очистки памяти. Но нужный формат для обучения word2vec из list превратился в str, чтобы избавиться от этой проблемы я воспользуюсь следующей библиотекой, которая превратит строку от списка в список."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "import ast\n",
    "\n",
    "\n",
    "def get_list(x):\n",
    "    return ast.literal_eval(x)\n",
    "data['content_clear'] = data['content_clear'].apply(lambda x: ast.literal_eval(x))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "model = word2vec.Word2Vec(data['content_clear'], size=300, window=10, workers=4)\n",
    "w2v = dict(zip(model.wv.index2word, model.wv.syn0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим чему выучилась модель"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.wv.most_similar(positive=['open', 'data','science','best'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "модель обучилась достаточно неплохо, посмотрим на результаты алгоритмов"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "data_mean = mean_vectorizer(w2v).fit(data['content_clear']).transform(data['content_clear'])\n",
    "data_mean.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def split(train,y,ratio):\n",
    "    idx = ratio\n",
    "    return train[:idx, :], train[idx:, :], y[:idx], y[idx:]\n",
    "y = data['favs_lognorm']\n",
    "Xtr, Xval, ytr, yval = split(data_mean, y,23425)\n",
    "Xtr.shape,Xval.shape,ytr.mean(),yval.mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.linear_model import Ridge\n",
    "from sklearn.metrics import mean_squared_error\n",
    "\n",
    "model = Ridge(alpha = 1,random_state=7)\n",
    "model.fit(Xtr, ytr)\n",
    "train_preds = model.predict(Xtr)\n",
    "valid_preds = model.predict(Xval)\n",
    "ymed = np.ones(len(valid_preds))*ytr.median()\n",
    "print('Ошибка на трейне',mean_squared_error(ytr, train_preds))\n",
    "print('Ошибка на валидации',mean_squared_error(yval, valid_preds))\n",
    "print('Ошибка на валидации предсказываем медиану',mean_squared_error(yval, ymed))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "data_mean_tfidf = tfidf_vectorizer(w2v).fit(data['content_clear']).transform(data['content_clear'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y = data['favs_lognorm']\n",
    "Xtr, Xval, ytr, yval = split(data_mean_tfidf, y,23425)\n",
    "Xtr.shape,Xval.shape,ytr.mean(),yval.mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = Ridge(alpha = 1,random_state=7)\n",
    "model.fit(Xtr, ytr)\n",
    "train_preds = model.predict(Xtr)\n",
    "valid_preds = model.predict(Xval)\n",
    "ymed = np.ones(len(valid_preds))*ytr.median()\n",
    "print('Ошибка на трейне',mean_squared_error(ytr, train_preds))\n",
    "print('Ошибка на валидации',mean_squared_error(yval, valid_preds))\n",
    "print('Ошибка на валидации предсказываем медиану',mean_squared_error(yval, ymed))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Попробуем нейронные сети."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from keras import regularizers\n",
    "from keras.layers import Activation, Dense, Dropout, Input\n",
    "# подключим библиотеки keras \n",
    "from keras.models import Model, Sequential\n",
    "from keras.preprocessing.text import Tokenizer\n",
    "from keras.wrappers.scikit_learn import KerasRegressor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def baseline_model():\n",
    "    model = Sequential()\n",
    "    model.add(Dense(128, input_dim=Xtr.shape[1], kernel_initializer='normal', activation='relu'))\n",
    "    model.add(Dropout(0.2))\n",
    "    model.add(Dense(64, activation='relu'))\n",
    "    model.add(Dropout(0.5))\n",
    "    model.add(Dense(1, kernel_initializer='normal'))\n",
    "\n",
    "    model.compile(loss='mean_squared_error', optimizer='adam')\n",
    "    return model\n",
    "estimator = KerasRegressor(build_fn=baseline_model,epochs=20, nb_epoch=20, batch_size=64,validation_data=(Xval, yval), verbose=2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "estimator.fit(Xtr, ytr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Получили более хороший результат по сравнению с ридж-регрессией."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Аналоги"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Стоит отметить, что Word2Vec не единственная технология. Например:\n",
    "\n",
    "1)[Glove](https://nlp.stanford.edu/projects/glove/), тут их репозиторий с объяснением работы, инструкцией и предобученными моделями на вики и твиттере;\n",
    "\n",
    "2)[AdaGram](https://github.com/lopuhin/python-adagram) репозиторий."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "# Вывод\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Мы рассмотрели принцип работы Word2Vec и его модификации, реализованные в библиотеки gensim.\n",
    "Применили Word2Vec в двух соревнованиях и поняли, что Word2Vec хороший способ преобразования нечисловых данных в вектора (сохраняя их смысловую близость), на которых уже можно обучать известные вам модели, без сильного увеличения размерности признакового пространства и с сохранением достойного качества.\n",
    "\n",
    "Что можно сделать еще?\n",
    "Ввиду небольшой размерности признакового пространства можно добавить новые признаки, основанные на времени, например различные статистические показатели на разницах во времени в переходах между страницами, датой публикации. Можно обучить еще один Word2Vec на тегах или описаниях и добавить, как новые фичи. Можно рассмотреть есть ли картинки в статье, ее длину и прочее.\n",
    "\n",
    "Спасибо за внимание!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Полезные ссылки"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "- [туториал Bag of Words Meets Bags of Popcorn на kaggle](https://www.kaggle.com/c/word2vec-nlp-tutorial)\n",
    "- [библиотека gensim](https://radimrehurek.com/gensim/index.html)\n",
    "- [Word2Vec Parameter Learning Explained, Xin Rong](https://arxiv.org/pdf/1411.2738.pdf)\n",
    "- [Distributed Representations of Words and Phrases and their Compositionality](https://papers.nips.cc/paper/5021-distributed-representations-of-words-and-phrases-and-their-compositionality.pdf)\n",
    "- [Negative-Sampling Word-Embedding Method](https://arxiv.org/pdf/1402.3722.pdf)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
